﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Security.Cryptography;
using System.Threading;

namespace FileSharing
{
    public class LocalFile
    {
        private static SHA1 sha = new SHA1CryptoServiceProvider();

        byte[] HashData;
        bool HasHash = false;
        bool FileCompleted;

        FileInfo Info;

        Mutex FileOperationMut = new Mutex();
        
        public static int PieceSize = 0x20000;
        public static int ChunkSize = 0x1000;

        public String Name { get { return Info.Name; } }
        public String Path { get { return Info.FullName; } }
        public Int64 Length { get { return Info.Length; } }
        public bool Completed { get { return FileCompleted; } }

        public byte[] Hash
        {
            get
            {
                if (!HasHash)
                {
                    FileOperationMut.WaitOne();

                    BinaryReader stream = new BinaryReader(Info.OpenRead());

                    try
                    {
                        int sizeToRead = (Info.Length < 0x40000) ? (int)Info.Length : 0x40000;

                        byte[] piece = stream.ReadBytes(sizeToRead);

                        HashData = sha.ComputeHash(piece);

                        HasHash = true;
                    }
                    finally
                    {
                        stream.Close();
                        FileOperationMut.ReleaseMutex();
                    }
                }

                return HashData;
            }
        }

        /// <summary>
        /// Creates a local file based on an existing file.
        /// </summary>
        /// <param name="file">Information of the file.</param>
        public LocalFile(FileInfo file)
        {
            Info = file;
            FileCompleted = true;
        }

        /// <summary>
        /// Creates a new local file with size Length.
        /// </summary>
        /// <param name="Path">The path which the file will be written.</param>
        /// <param name="Length">Length of the file.</param>
        public LocalFile(string Path, Int64 Length)
        {
            FileOperationMut.WaitOne();

            FileStream stream = File.Create(Path);

            stream.SetLength(Length);

            stream.Close();

            Info = new FileInfo(Path);

            FileOperationMut.ReleaseMutex();
        }

        public byte[] AvaiblePieces()
        {
            double size = Info.Length / 8.0;
            byte[] pieces = new byte[(int)Math.Ceiling(size)];

            for (int i = 0; i < pieces.Length; i++)
                pieces[i] = 0xFF;

            return pieces;
        }

        public int PieceCount()
        {
            return (int)Math.Ceiling((double)Info.Length / LocalFile.PieceSize);
        }

        public Piece GetPiece(int sequenceNumber)
        {
            long size = Math.Min(Info.Length - PieceSize * sequenceNumber, (long)PieceSize);
            byte[] chunk = new byte[size];

            FileOperationMut.WaitOne();

            FileStream stream = Info.OpenRead();

            try
            {
                stream.Seek(PieceSize * sequenceNumber, SeekOrigin.Begin);

                stream.Read(chunk, 0, (int)size);

                return new Piece(chunk, sequenceNumber);
            }
            finally
            {
                stream.Close();
                FileOperationMut.ReleaseMutex();
            }
        }

        public void WritePiece(Piece piece)
        {
            FileOperationMut.WaitOne();

            FileStream stream = Info.OpenWrite();

            try
            {
                byte[] Data = piece.GetData();

                stream.Seek(piece.SequenceNumber * PieceSize, SeekOrigin.Begin);

                stream.Write(Data, 0, Data.Length);
            }
            finally
            {
                stream.Close();
                FileOperationMut.ReleaseMutex();
            }
        }

        public static bool operator ==(LocalFile f1, LocalFile f2)
        {
            bool equal = true;
            for (int i = 0; i < 20; i++)
            {
                if (f1.Hash[i] != f2.Hash[i])
                {
                    equal = false;
                    break;
                }
            }

            if (equal && f1.Length == f2.Length)
                return true;

            return false;
        }

        public static bool operator !=(LocalFile f1, LocalFile f2)
        {
            bool equal = true;
            for (int i = 0; i < 20; i++)
            {
                if (f1.Hash[i] != f2.Hash[i])
                {
                    equal = false;
                    break;
                }
            }

            if (equal && f1.Length == f2.Length)
                return false;

            return true;
        }
    }

    public class RemoteFile
    {
        byte[] HashData;
        String FileName;
        Int64 FileLength;

        public String Name { get { return FileName; } }
        public Int64 Length { get { return FileLength; } }
        public byte[] Hash { get { return HashData; } }
        public Vizinho Owner { get; set; }

        public RemoteFile(BinaryReader stream)
        {
            int nameSize = stream.ReadInt32();
            char[] nameData = stream.ReadChars(nameSize);

            FileLength = stream.ReadInt64();
            HashData = stream.ReadBytes(20);

            FileName = new String(nameData);
        }

        public RemoteFile(LocalFile file)
        {
            FileName = file.Name;
            FileLength = file.Length;
            HashData = file.Hash;
        }

        public int Serialize(BinaryWriter stream)
        {
            stream.Write(FileName.Length);
            stream.Write(FileName.ToCharArray(), 0, FileName.Length);
            stream.Write(FileLength);
            stream.Write(Hash, 0, 20);

            return sizeof(int) + FileName.Length + sizeof(long) + 20;
        }

        public int PieceCount()
        {
            return (int)Math.Ceiling((double)Length / LocalFile.PieceSize);
        }

        public static bool operator ==(RemoteFile f1, RemoteFile f2)
        {
            bool equal = true;
            for (int i = 0; i < 20; i++)
            {
                if (f1.Hash[i] != f2.Hash[i])
                {
                    equal = false;
                    break;
                }
            }

            if (equal && f1.Length == f2.Length)
                return true;

            return false;
        }

        public static bool operator !=(RemoteFile f1, RemoteFile f2)
        {
            bool equal = true;
            for (int i = 0; i < 20; i++)
            {
                if (f1.Hash[i] != f2.Hash[i])
                {
                    equal = false;
                    break;
                }
            }

            if (equal && f1.Length == f2.Length)
                return false;

            return true;
        }
    }
}
